//! command instruction data;

use anyhow::{Result, anyhow};

#[derive(Debug)]
pub struct Comp {
    pub code: &'static str,
    pub bin: u8,
}
impl Comp {
    const DATA: [Self; 28] = [
        Self::new("0", 0b101010),
        Self::new("1", 0b111111),
        Self::new("-1", 0b111010),
        Self::new("D", 0b001100),
        Self::new("A", 0b110000),
        Self::new("M", 0b1110000),
        Self::new("!D", 0b001101),
        Self::new("!A", 0b110001),
        Self::new("!M", 0b1110001),
        Self::new("-D", 0b001111),
        Self::new("-A", 0b110011),
        Self::new("-M", 0b1110011),
        Self::new("D+1", 0b011111),
        Self::new("A+1", 0b110111),
        Self::new("M+1", 0b1110111),
        Self::new("D-1", 0b001110),
        Self::new("A-1", 0b110010),
        Self::new("M-1", 0b1110010),
        Self::new("D+A", 0b000010),
        Self::new("D+M", 0b1000010),
        Self::new("D-A", 0b010011),
        Self::new("D-M", 0b1010011),
        Self::new("A-D", 0b000111),
        Self::new("M-D", 0b1000111),
        Self::new("D&A", 0b000000),
        Self::new("D&M", 0b1000000),
        Self::new("D|A", 0b010101),
        Self::new("D|M", 0b1010101),
    ];
    const fn new(code: &'static str, bin: u8) -> Self {
        Self { code, bin }
    }
    /// get the code's bin code
    ///
    /// # arguments
    ///
    /// * code : the assember code
    pub fn bin(code: &str) -> Result<u8> {
        Self::DATA
            .iter()
            .find_map(|c| (c.code == code).then_some(c.bin))
            .ok_or_else(|| anyhow!("invalid code: {}", code))
    }
}

#[derive(Debug, Default)]
pub struct Dest {
    pub m: bool,
    pub d: bool,
    pub a: bool,
}
impl Dest {
    /// get the code's bin code
    ///
    /// # arguments
    ///
    /// * code : the assember code
    pub fn bin(code: Option<&str>) -> Result<u8> {
        Self::new(code).map(|dest| dest.to_bin())
    }
    fn new(code: Option<&str>) -> Result<Self> {
        if let Some(code) = code {
            code.chars()
                .all(|c| ['M', 'D', 'A'].contains(&c))
                .then(|| Self {
                    m: code.contains('M'),
                    d: code.contains('D'),
                    a: code.contains('A'),
                })
                .ok_or_else(|| anyhow!("invalid dest code: {}", code))
        } else {
            Ok(Self::default())
        }
    }
    fn to_bin(&self) -> u8 {
        let mut bin = 0;
        if self.m {
            bin |= 0b001;
        }
        if self.d {
            bin |= 0b010;
        }
        if self.a {
            bin |= 0b100;
        }
        bin
    }
}

#[derive(Debug, Default)]
pub struct Jump {
    pub gt: bool,
    pub eq: bool,
    pub lt: bool,
}

impl Jump {
    /// get the code's bin code
    ///
    /// # arguments
    ///
    /// * code : the assember code
    pub fn bin(code: Option<&str>) -> Result<u8> {
        Self::new(code).map(|jmp| jmp.to_bin())
    }
    fn new(code: Option<&str>) -> Result<Self> {
        if let Some(code) = code {
            ["JGT", "JEQ", "JGE", "JLT", "JNE", "JLE", "JMP"]
                .contains(&code)
                .then(|| Self {
                    gt: code.contains(['G', 'N', 'M']),
                    eq: !code.ends_with("NE") && code.contains(['E', 'M']),
                    lt: code.contains(['L', 'N', 'M']),
                })
                .ok_or_else(|| anyhow!("invalid jump code: {}", code))
        } else {
            Ok(Self::default())
        }
    }
    fn to_bin(&self) -> u8 {
        let mut bin = 0;
        if self.gt {
            bin |= 0b001;
        }
        if self.eq {
            bin |= 0b010;
        }
        if self.lt {
            bin |= 0b100;
        }
        bin
    }
}

#[cfg(test)]
mod test;
